#include "stock_client.h"
#include "build/build_config.h"

#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

/*
 *
 */
#if defined (OS_WIN)
#define  socklen_t int
#include <winsock.h>
#else 
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif


/*
 *
 */
static int                server_sock = 0;
static struct sockaddr_in server_addr;
static int                server_len = 0;


/*
 *
 */
static int first_send = 1;
static int time_delta = 0;


/*
 *
 */
#define STOCK_SERVER_IP   "98.210.96.243\0"
#define STOCK_SERVER_PORT  23
#define STOCK_HEADER_LEN   8
#define STOCK_TICKER_LEN   20


/*
 *
 */
static char *
stock_server_ip()
{
    return STOCK_SERVER_IP;
}


/*
 *
 */
static int
stock_server_stream_connect(char *ip)
{
    /*
     * Initialize the server address if it's the first time we're here
     */
    if (server_len == 0) {
        printf("Initializing Server Address\n");

        server_len = sizeof(server_addr);
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port   = htons(STOCK_SERVER_PORT);

        #if defined (OS_WIN)
        server_addr.sin_addr.S_un.S_addr = inet_addr(ip);
        #else
        if (inet_aton(ip, &server_addr.sin_addr) == 0) {
            printf("ERROR: inet_aton()\n");
            return -1;
        }
        #endif

    }

    /*
     * Initialize the server socket if it's the first itme we're here OR if we
     * closed the socket before due to update send error
     */
    if (server_sock == 0) {
        printf("Connecting to server\n");

        if ((server_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
            printf("ERROR: socket\n");

            #if defined (OS_WIN)
            closesocket(server_sock);
            #else
            close(server_sock);
            #endif

            return -1;
        }

        if (connect(server_sock, (struct sockaddr *)&server_addr, server_len) < 0) {
            printf("ERROR: connect\n");
        }
    }

    return 0;
}


/*
 *
 */
typedef struct stock_data_t {
    uint32_t timestamp;
    uint32_t price;
    uint32_t change;
} stock_data;

typedef struct stock_update_t {
    struct in_addr client;
    char           ticker[STOCK_TICKER_LEN];
    stock_data     data;
} stock_update;

typedef struct stock_update_msg_t {
    unsigned char header[STOCK_HEADER_LEN];
    stock_update  update;
} stock_update_msg;


typedef struct stock_timesync_t {
    uint32_t delta;
} stock_timesync;

typedef struct stock_timesync_msg_t {
    unsigned char  header[STOCK_HEADER_LEN];
    stock_timesync timesync;
} stock_timesync_msg;

#define STOCK_UPDATE_MSG_SIZE   (sizeof(stock_update_msg))
#define STOCK_TIMESYNC_MSG_SIZE (sizeof(stock_timesync_msg))


/*
 *
 */
uint32_t
stock_system_time()
{
    struct timeval now;
    now.tv_sec = time(NULL);
    return (uint32_t) now.tv_sec;
}


/*
 *
 */
uint32_t
stock_adj_time()
{
    return (stock_system_time() + time_delta);
}


/*
 *
 */
char *
stock_time_string(time_t *time) 
{
    char *tp, *p = NULL;

    tp = ctime(time);
    for (p = tp; *p != '\n'; p++);
    *p = '\0';

    return tp;
}


/*
 *
 */
static int 
stock_update_format(stock_update *upd, stock_update_msg *msg) 
{
    memset(msg->header, 0xFF, STOCK_HEADER_LEN);
    memcpy(msg->update.ticker, upd->ticker, STOCK_TICKER_LEN);
    msg->update.data.timestamp = htonl(upd->data.timestamp);
    msg->update.data.price = htonl(upd->data.price);
    msg->update.data.change = htonl(upd->data.change);

    return sizeof(stock_update_msg);
}


/*
 *
 */
static int 
stock_update_send(int sock, struct sockaddr_in *addr, stock_update *upd) 
{
    struct sockaddr_in *server;
    int                 server_len = sizeof(struct sockaddr_in);
    char                server_buf[STOCK_UPDATE_MSG_SIZE];
    stock_update_msg   *msg;
    const char         *pmsg;
    int                 msgsize;

    server = addr;

    memset(server_buf, 0, STOCK_UPDATE_MSG_SIZE);
    msg = (stock_update_msg *) server_buf; 
    msgsize = stock_update_format(upd, msg);

    pmsg = (const char *) msg;

    if (sendto(sock, pmsg, msgsize, 0, 
              (struct sockaddr*) server, server_len) == -1) {
        fprintf(stderr, "sendto() failed to %s\n", inet_ntoa(server->sin_addr));
        return -1;
    }

    return 0;
}


/*
 *
 */
int
stock_verify_header(void *m)
{
    int            index;
    unsigned char *msg;

    msg = (unsigned char *) m;

    for (index = 0 ; index < STOCK_HEADER_LEN; index++) {
        if (msg[index] != 0xFF) {
            return -1;
        }
    }

    return 0;
}


/*
 *
 */
static int
stock_timesync_parse(stock_timesync_msg *msg, stock_timesync *ts)
{
    if (stock_verify_header(msg) != 0) {
        return -1;
    }

    ts->delta = ntohl(msg->timesync.delta);
    return 0;
}


/*
 *
 */
static int
stock_timesync_recv(int sock, stock_timesync *sync) 
{
    struct sockaddr_in  client;
    socklen_t           client_len;
    char                client_buf[STOCK_TIMESYNC_MSG_SIZE];
    stock_timesync_msg *msg;
	char               *pmsg;
    int                 rc;

    msg = (stock_timesync_msg *) client_buf; 
	pmsg = (char *) msg;

    memset(sync, 0, sizeof(stock_timesync));
    memset(client_buf, 0, STOCK_TIMESYNC_MSG_SIZE);

    rc = recvfrom(sock, pmsg, STOCK_TIMESYNC_MSG_SIZE, 0, 
                 (struct sockaddr*) &client, &client_len);

    if (rc == STOCK_TIMESYNC_MSG_SIZE) {  
        if (stock_timesync_parse(msg, sync) != 0) {
            return -1;
        }
    }

    return 0;
}


/*
 *
 */
static void
stock_server_timesync(int sock) 
{
    stock_timesync timesync;

    #if defined (OS_WIN)
	u_long win_flags;
    ioctlsocket(sock, FIONBIO, &win_flags);
    #else
	int so_flags;
    so_flags = fcntl(sock, F_GETFL);
    so_flags |= O_NONBLOCK;
    fcntl(sock, F_SETFL, so_flags);
    #endif

    memset(&timesync, 0, sizeof(timesync));

    if (first_send) {
        #if defined (OS_WIN)
		Sleep(3000);
        #else
		sleep(3);
        #endif
        
        stock_timesync_recv(sock, &timesync);
        time_delta += (int) timesync.delta;
        first_send = 0;
    }
}


/*
 *
 */
static int 
stock_google_portfolio_inner_html_hook_v1(const char *html, 
                                          HTMLElement *node,
                                          stock_update *update) 
{
    HTMLElement     *p, *q, *r, *s, *t;
    WebCore::String  stock, ticker;
    WebCore::String  price_str;
     
    /*
     * Below list of checks filters out the nodes that update the stock change
     * percentage field in a google portfolio
     */
    if (node->tagName() != "SPAN") return -1;
    if ((p = node->up()) == NULL) return -1;
    if (p->tagName() != "TD") return -1;
    if (!p->hasAttribute("class")) return -1;
    if (!p->getAttribute("class").contains("pf-table-cp")) return -1;
    if (!p->getAttribute("class").contains("pf-table-cell")) return -1;
    if (!node->hasAttribute("id")) return -1;
    if (!node->getAttribute("id").contains("_cp")) return -1;


    /*
     * Now, move up the tree to get the whole row describing the stock
     */
    if ((p = p->up()) == NULL) return -1;
    if (p->tagName() != "TR") return -1;

    /*
     * This is the whole row element;
     */
    r = p;

    /*
     * Go down to get the stock name
     */
    if ((p = p->down()) == NULL) return -1;
    if ((p = p->next()) == NULL) return -1;
    if (p->tagName() != "TD") return -1;
    if ((p = p->down()) == NULL) return -1;
    if (!p->hasAttribute("title")) return -1;

    stock = p->getAttribute("title");

    /*
     * Get the stock ticker
     */
    if ((p = r->down()) == NULL) return -1;
    if ((p = p->next()) == NULL) return -1;
    if ((p = p->next()) == NULL) return  -1;
    /* TODO: Check classname of p */
    if ((p = p->down()) == NULL) return -1;
    if (p->tagName() != "A") return -1;

    ticker = p->innerHTML();

    /*
     * Get the stock price
     */
    if ((p = r->down()) == NULL) return -1;
    if ((p = p->next()) == NULL) return -1;
    if ((p = p->next()) == NULL) return -1;  
    if ((p = p->next()) == NULL) return -1; 
    /* TODO: Check classname of p */
    if ((p = p->down()) == NULL) return -1;

    /*
     * p should be a span element with class "prb". inside it should be two
     * child span elements whose inner html's concatenated give the stock
     * price 
     */
    p = p->down();
    price_str.append(p->innerHTML());
    p = p->next();
    price_str.append(p->innerHTML());
    price_str.remove(price_str.find("."));


    if (server_sock == 0) return -1;

    /*
     * Set up the update to send out
     */
    memset(update, 0, sizeof(stock_update));
    update->data.price = price_str.toInt();
    update->data.timestamp = stock_adj_time();

    memcpy(update->ticker, ticker.utf8().data(), STOCK_TICKER_LEN);

    return 0;
}


/*
 *
 */
void 
stock_client_inner_html_hook(const char *html, HTMLElement *node) 
{
    stock_update update;
    int          rc;
    time_t       tm = time(NULL);

    rc = stock_server_stream_connect(stock_server_ip());

    rc = stock_google_portfolio_inner_html_hook_v1(html, node, &update);

    if (rc !=  0) {
        return;
    }

    rc = stock_update_send(server_sock, &server_addr, &update);

    if (rc != 0) {
        server_sock = 0;
        return;
    } else {
        /*
         * XXX: here only for debugging purposes
         */

        printf("[%s] %6s @ %6d\n", stock_time_string(&tm), update.ticker, update.data.price);
    }

    stock_server_timesync(server_sock);
}

